/* 
 *  Syntelos XB
 *  Copyright (C) 2006 John Pritchard.
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License as
 *  published by the Free Software Foundation; either version 2 of
 *  the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 *  02111-1307 USA
 */

package syntelos.xb.at;

/** 
 * <p> Entry in {@link LocalVariableTable}. </p>
 * 
 * @author jdp
 */
public class Variable 
    extends syntelos.xb.Base
    implements java.lang.Comparable
{
    public final static syntelos.iou.dom.Name NAME = new syntelos.iou.dom.impl.Name(ELEMENT_NODE,"http://www.syntelos.org/xb/at","xat:variable");
    
    /** 
     * <p> Slot component of variable. </p>
     * 
     */
    public final static class Slot 
        extends syntelos.xb.op.Operand
    {
        public final static syntelos.iou.dom.Name NAME = new syntelos.iou.dom.impl.Name(ELEMENT_NODE,"http://www.syntelos.org/xb/at","xat:variable.slot");

        public Slot(org.w3c.dom.Document doc, syntelos.iou.dom.Name name){
            super(doc,name);
        }
        public Slot(org.w3c.dom.Document doc){
            this(doc,NAME);
        }
        public Slot(){
            this(DOC_NIL);
        }

        protected syntelos.xb.at.Variable getVariable(){
            return (syntelos.xb.at.Variable)this.getParentNode();
        }
        public syntelos.xb.op.Code getOpCode(){
            return this.getVariable().getOpCode();
        }
        public syntelos.xb.at.Code getAttributeCode(){
            return this.getVariable().getAttributeCode();
        }
        public syntelos.xb.at.LocalVariableTable getAttributeLocalVariableTable(){
            return this.getVariable().getAttributeLocalVariableTable();
        }

        public int getSlot(){
            return this.getOperand();
        }
        public void setSlot(int slot){
            this.setOperand(slot);
            this.getVariable().setId(slot);
        }
        public void readCF(syntelos.xb.IO.Uri src, syntelos.xb.CF.Reader reader)
            throws java.io.IOException
        {
            int slot = reader.readUnsignedShort();
            this.setSlot(slot);
        }
        public void writeCF(syntelos.xb.IO.Uri dst, syntelos.xb.CF.Writer writer)
            throws java.io.IOException
        {
            int slot = this.getSlot();
            writer.writeShort(slot);
        }
    }
    /** 
     * <p> Scope component of variable. </p>
     * 
     */
    public final static class Scope 
        extends syntelos.xb.Base
    {
        public final static syntelos.iou.dom.Name NAME = new syntelos.iou.dom.impl.Name(ELEMENT_NODE,"http://www.syntelos.org/xb/at","xat:variable.scope");

        /**
         * <p> Start offset PC. </p>
         */
        public final static class Start
            extends syntelos.xb.op.Operand.Branch
        {
            public final static syntelos.iou.dom.Name NAME = new syntelos.iou.dom.impl.Name(ELEMENT_NODE,"http://www.syntelos.org/xb/at","xat:variable.scope.start");

            public Start(org.w3c.dom.Document doc, syntelos.iou.dom.Name name){
                super(doc,name);
            }
            public Start(org.w3c.dom.Document doc){
                this(doc,NAME);
            }
            public Start(){
                this(DOC_NIL);
            }

            protected syntelos.xb.at.Variable.Scope getVariableScope(){
                return (syntelos.xb.at.Variable.Scope)this.getParentNode();
            }
            /** @see syntelos.xb.Base
             */
            public LocalVariableTable getAttributeLocalVariableTable(){
                return this.getVariableScope().getAttributeLocalVariableTable();
            }
            public syntelos.xb.at.Code getAttributeCode(){
                return this.getVariableScope().getAttributeCode();
            }
            /** @see syntelos.xb.Base
             */
            public syntelos.xb.op.Code getOpCode(){
                return this.getVariableScope().getOpCode();
            }
            /** @see syntelos.xb.Base
             */
            public syntelos.xb.Method getMethod(){
                return this.getVariableScope().getMethod();
            }
        }

        /**
         * <p> Scope length from start. </p>
         */
        public final static class Length
            extends syntelos.xb.op.Operand
        {
            public final static syntelos.iou.dom.Name NAME = new syntelos.iou.dom.impl.Name(ELEMENT_NODE,"http://www.syntelos.org/xb/at","xat:variable.scope.length");

            public Length(org.w3c.dom.Document doc, syntelos.iou.dom.Name name){
                super(doc,name);
            }
            public Length(org.w3c.dom.Document doc){
                this(doc,NAME);
            }
            public Length(){
                this(DOC_NIL);
            }

            protected syntelos.xb.at.Variable.Scope getVariableScope(){
                return (syntelos.xb.at.Variable.Scope)this.getParentNode();
            }
            /** @see syntelos.xb.Base
             */
            public LocalVariableTable getAttributeLocalVariableTable(){
                return this.getVariableScope().getAttributeLocalVariableTable();
            }
            public syntelos.xb.at.Code getAttributeCode(){
                return this.getVariableScope().getAttributeCode();
            }
            /** @see syntelos.xb.Base
             */
            public syntelos.xb.op.Code getOpCode(){
                return this.getVariableScope().getOpCode();
            }
            /** @see syntelos.xb.Base
             */
            public syntelos.xb.Method getMethod(){
                return this.getVariableScope().getMethod();
            }
        }
        
        protected static class Users 
            extends java.lang.Object
        {
            private syntelos.xb.Op.Variable[] list;

            protected Users(){
                super();
            }
            private boolean add(syntelos.xb.Op.Variable op){
                if (null != op){
                    syntelos.xb.Op.Variable[] list = this.list;
                    if (null == list)
                        this.list = new syntelos.xb.Op.Variable[]{op};
                    else {
                        int len = list.length;
                        syntelos.xb.Op.Variable[] copier = new syntelos.xb.Op.Variable[len+1];
                        System.arraycopy(list,0,copier,0,len);
                        list = copier;
                        list[len] = op;
                        this.list = list;
                    }
                    return true;
                }
                else
                    return false;
            }
            private syntelos.xb.Op.Variable item(int idx){
                syntelos.xb.Op.Variable[] list = this.list;
                if (null == list)
                    return null;
                else if (-1 < idx && idx < list.length)
                    return list[idx];
                else
                    return null;
            }
            private int getLength(){
                syntelos.xb.Op.Variable[] list = this.list;
                if (null == list)
                    return 0;
                else
                    return list.length;
            }

            protected void compile(syntelos.xb.Op.Variable op, Scope scope){
                this.add(op);
                /*
                 * Light init for OPs without PCs
                 */
                Start scope_start = scope.getStartOperand();
                if (null != scope_start){
                    if (null == scope_start.getChildByIndex(0))
                        scope_start.setOperand(op);
                }
            }
            protected void compile(Scope scope){
                int len = this.getLength();
                if (0 < len){
                    int min = 0, max = 0,
                        idx = 0, pc;
                    syntelos.xb.Op.Variable op, 
                        start = null;
                    for ( ; idx < len; idx++){
                        op = this.item(idx);
                        pc = op.getPC();
                        if (0 == idx){
                            min = pc;
                            max = pc;
                        }
                        else {
                            min = Math.min(min,pc);
                            if (min == pc)
                                start = op;
                            max = Math.max(max,pc);
                        }
                    }
                    //
                    if (null != start){
                        len = (max-min);
                        //
                        Start scope_start = scope.getStartOperand();
                        scope_start.setOperand(start);
                        //
                        Length scope_length = scope.getLengthOperand();
                        if (0 < len)
                            scope_length.setOperand(len);
                        return;
                    }
                }
                else {
                    Start scope_start = scope.getStartOperand();
                    if (null == scope_start.getChildText())
                        scope_start.setOperand(0);
                    Length scope_length = scope.getLengthOperand();
                    if (null == scope_length.getChildText()){
                        syntelos.xb.op.Code code = scope.getOpCode();
                        len = code.countOp();
                        scope_length.setOperand(len-1);
                    }
                    return;
                }
            }
        }

        private Users users = new Users();

        public Scope(org.w3c.dom.Document doc, syntelos.iou.dom.Name name){
            super(doc,name);
        }
        public Scope(org.w3c.dom.Document doc){
            this(doc,NAME);
        }
        public Scope(){
            this(DOC_NIL);
        }

        protected Variable getVariable(){
            return (Variable)this.getParentNode();
        }
        /** @see syntelos.xb.Base
         */
        public syntelos.xb.op.Code getOpCode(){
            return this.getVariable().getOpCode();
        }
        /** @see syntelos.xb.Base
         */
        public syntelos.xb.Method getMethod(){
            return this.getVariable().getMethod();
        }
        /** @see syntelos.xb.Base
         */
        public syntelos.xb.at.Code getAttributeCode(){
            return this.getVariable().getAttributeCode();
        }
        /** @see syntelos.xb.Base
         */
        public syntelos.xb.at.LocalVariableTable getAttributeLocalVariableTable(){
            return this.getVariable().getAttributeLocalVariableTable();
        }
        public Start getStartOperand(){
            return this.getStartOperand(true);
        }
        public Start getStartOperand(boolean create){
            Start start = (Start)this.getChildByName("variable.scope.start");
            if (null == start && create){
                start = new Start();
                this.appendChild(start);
            }
            return start;
        }
        public int getScopeStart(){
            return this.getStartOperand().getOperand();
        }
        public void setScopeStart(int pc){
            this.getStartOperand().setOperand(pc);
        }
        public void setScopeStart(syntelos.xb.Op op){
            this.getStartOperand().setOperand(op);
        }
        public Length getLengthOperand(){
            return this.getLengthOperand(true);
        }
        public Length getLengthOperand(boolean create){
            Length length = (Length)this.getChildByName("variable.scope.length");
            if (null == length && create){
                length = new Length();
                this.appendChild(length);
            }
            return length;
        }
        public int getScopeLength(){
            return this.getLengthOperand().getOperand();
        }
        public void setScopeLength(int length){
            this.getLengthOperand().setOperand(length);
        }
        public void compileUser(syntelos.xb.Op.Variable op){

            this.users.compile(op,this);
        }
        public void compileUsers(){

            this.users.compile(this);
        }

        public void readCF(syntelos.xb.IO.Uri src, syntelos.xb.CF.Reader reader)
            throws java.io.IOException
        {
            int pc_start = reader.readUnsignedShort();
            this.setScopeStart(pc_start);
            int pc_length = reader.readUnsignedShort();
            this.setScopeLength(pc_length);
        }
        public void writeCF(syntelos.xb.IO.Uri dst, syntelos.xb.CF.Writer writer)
            throws java.io.IOException
        {
            int pc_start = this.getScopeStart();
            writer.writeShort(pc_start);
            int pc_length = this.getScopeLength();
            writer.writeShort(pc_length);
        }
    }
    
    public Variable(org.w3c.dom.Document doc, syntelos.iou.dom.Name name){
        super(doc,name);
    }
    public Variable(org.w3c.dom.Document doc){
        this(doc,NAME);
    }
    public Variable(){
        this(DOC_NIL);
    }

    /** @see syntelos.xb.Base
     */
    public LocalVariableTable getAttributeLocalVariableTable(){
        return (LocalVariableTable)this.getParentNode();
    }
    /** @see syntelos.xb.Base
     */
    public syntelos.xb.op.Code getOpCode(){
        return this.getAttributeLocalVariableTable().getOpCode();
    }
    /** @see syntelos.xb.Base
     */
    public syntelos.xb.at.Code getAttributeCode(){
        return this.getAttributeLocalVariableTable().getAttributeCode();
    }
    /** @see syntelos.xb.Base
     */
    public syntelos.xb.Method getMethod(){
        return this.getAttributeLocalVariableTable().getMethod();
    }
    /**
     * @return True for incomplete URI, this needs slot 
     */
    protected boolean define(syntelos.xb.IO.Uri uri){
        if ("variable".equals(uri.getSchemeHead())){
            java.lang.String name = uri.getPath();
            if (null != name){
                this.getSlot();
                this.getName().setName(name);
                syntelos.xb.Type.Descriptor type = this.getType();
                String type_string = uri.getQuery("type");
                if (null != type_string){
                    syntelos.xb.IO.Uri type_uri = 
                        new syntelos.xb.IO.Uri(type_string);
                    syntelos.xb.CF.Constants constants = this.getConstants();
                    syntelos.xb.CF.Constants.Utf8 constant = constants.constUtf8(type_uri);
                    type.setDescriptorConst(constant);
                    //
                    java.lang.String slot_string = uri.getQuery("slot");
                    if (null != slot_string){
                        /*(Complete)
                         */
                        int slot = syntelos.xb.IO.Uri.Integer(slot_string);
                        this.getSlot().setSlot(slot);
                        this.setId(slot);
                        Variable.Scope scope = this.getScope();
                        java.lang.String start_string = uri.getQuery("scope-start");
                        if (null != start_string){
                            int scope_start = syntelos.xb.IO.Uri.Integer(start_string);
                            scope.setScopeStart(scope_start);
                        }
                        java.lang.String length_string = uri.getQuery("scope-length");
                        if (null != length_string){
                            int scope_length = syntelos.xb.IO.Uri.Integer(length_string);
                            scope.setScopeLength(scope_length);
                        }
                        return false;
                    }
                    else {
                        /*(Incomplete)
                         */
                        this.getScope();
                        return true;
                    }
                }
                else
                    throw new syntelos.xb.Error.Format("Variable reference missing 'type' parameter '"+uri.toString()+"'.");
            }
            else
                throw new syntelos.xb.Error.Format("Variable reference missing 'name' path component '"+uri.toString()+"'.");
        }
        else
            throw new syntelos.xb.Error.Format("Unrecognized variable reference '"+uri.toString()+"'.");
    }
    public void setId(int slot){
        super.setId(slot);
        LocalVariableTable locals = this.getAttributeLocalVariableTable();
        Object test = locals.getVar(slot);
        if (this == test)
            return;
        else {
            int slot2 = (slot+1);
            int caution = (slot2 + locals.countChildNodes());/*(something that will
                                                              * always work, even
                                                              * for supercompiled
                                                              * code)
                                                              */
            for (; slot2 < caution; slot2++){
                /*
                 * JAVAC generates code using slot numbers in a loose
                 * way: it reuses slot numbers (according to scope) in
                 * the var structure, but references variables of
                 * reused slot by index from bytecode.
                 * 
                 * Of course the implication is that the variable
                 * operand is not the reused slot number, but an
                 * index, the jvm constructs variables staticly (not
                 * reused) into unused slots with overflow, and the
                 * compiler is wasting its time (space) reusing
                 * (scoping) variable slots.
                 */
                super.setId(slot2);
                if (this == locals.getVar(slot2))
                    return;
            }
            throw new syntelos.xb.Error.Bug("Variable slot ("+slot+") unavailable within ("+caution+") positions?");
        }
    }
    public syntelos.xb.Name getName(){
        return this.getName(true);
    }
    public syntelos.xb.Name getName(boolean create){
        syntelos.xb.Name name = (syntelos.xb.Name)this.getChildByName("name");
        if (null == name && create){
            name = new syntelos.xb.Name();
            this.appendChild(name);
        }
        return name;
    }
    public syntelos.xb.Type.Descriptor getType(){
        return this.getType(true);
    }
    public syntelos.xb.Type.Descriptor getType(boolean create){
        syntelos.xb.Type.Descriptor type = (syntelos.xb.Type.Descriptor)this.getChildByName("type.descriptor");
        if (null == type && create){
            type = new syntelos.xb.Type.Descriptor();
            this.appendChild(type);
        }
        return type;
    }
    public Variable.Slot getSlot(){
        return this.getSlot(true);
    }
    public Variable.Slot getSlot(boolean create){
        Variable.Slot slot = (Variable.Slot)this.getChildByName("variable.slot");
        if (null == slot && create){
            slot = new Variable.Slot();
            this.appendChild(slot);
        }
        return slot;
    }
    public int getSlotValue(){
        return this.getSlot().getSlot();
    }
    public Variable.Scope getScope(){
        return this.getScope(true);
    }
    public Variable.Scope getScope(boolean create){
        Variable.Scope scope = (Variable.Scope)this.getChildByName("variable.scope");
        if (null == scope && create){
            scope = new Variable.Scope();
            this.appendChild(scope);
        }
        return scope;
    }
    /**
     * <p> Called from {@link
     * syntelos.xb.Op$Variable#setOperand(syntelos.xb.at.Variable)}
     * to maintain a dynamic scope of variable users for write- time
     * consolidation via {@link #toString()} and {@link
     * #writeCF(syntelos.xb.IO.Uri,syntelos.xb.CF.Writer)}
     * calls to {@link #compileUsers()}. </p>
     */
    public void compileUser(syntelos.xb.Op.Variable op){
        this.getScope().compileUser(op);
    }
    /**
     * @see #compileUser(syntelos.xb.Op$Variable)
     */
    public void compileUsers(){
        this.getScope().compileUsers();
    }

    /**
     * <p> XB reference string <code>"variable:name"</code>. 
     * </p>
     */
    public java.lang.String toString(){
        //
        this.compileUsers();
        //
        syntelos.iou.chbuf strbuf = new syntelos.iou.chbuf();
        strbuf.append("variable:");
        strbuf.append(this.getName().toString());
        strbuf.append("?slot=");
        int slot = this.getSlot().getSlot();
        strbuf.append(java.lang.String.valueOf(slot));
        strbuf.append("?type=");
        strbuf.append(syntelos.iou.url.encode(this.getType().toString()));

        Variable.Scope scope = this.getScope(false);
        if (null != scope){
            Variable.Scope.Start start = scope.getStartOperand(false);
            if (null != start){
                strbuf.append("?scope-start=");
                strbuf.append(start.getOperandOp().toString());
                Variable.Scope.Length length = scope.getLengthOperand(false);
                if (null != length){
                    strbuf.append("?scope-length=");
                    strbuf.append(java.lang.String.valueOf(length.getOperand()));
                }
            }
        }
        return strbuf.toString();
    }

    public void readCF(syntelos.xb.IO.Uri src, syntelos.xb.CF.Reader reader)
        throws java.io.IOException
    {
        this.getScope().readCF(src,reader);
        this.getName().readCF(src,reader);
        this.getType().readCF(src,reader);
        this.getSlot().readCF(src,reader);
    }
    public void writeCF(syntelos.xb.IO.Uri dst, syntelos.xb.CF.Writer writer)
        throws java.io.IOException
    {
        //
        this.compileUsers();
        //
        this.getScope().writeCF(dst,writer);
        this.getName().writeCF(dst,writer);
        this.getType().writeCF(dst,writer);
        this.getSlot().writeCF(dst,writer);
    }
}
